﻿Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.DirectoryServices.ActiveDirectory
Imports System.Text

''' <summary>
''' Directory にアクセスするための静的メソッド・プロパティを提供するクラスです。このクラスは継承できません。
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class DirectoryAccess

#Region " プライベートフィールド "
  Private Shared _categoryNames As ReadOnlyCollection(Of String)  'ディレクトリ オブジェクトの種類の名前リスト
  Private Shared _domainName, _ntPath As String     'ドメイン名, WinNTプロバイダのパス
  Private Shared _domLogon, _canConnect As Boolean  'ドメインにログオンしているかどうか, ドメインに接続できるかどうか
  Private Shared _groupTokens As Dictionary(Of Integer, String)   'ドメイングループのプライマリ グループ トークンと名前の組のリスト
#End Region

#Region " プライベートコンストラクター "
  ''' <summary>DirectoryAccess クラスの新しいインスタンスを初期化します。</summary>
  ''' <remarks></remarks>
  Private Sub New()
  End Sub
#End Region

#Region " パブリックプロパティ "
  ''' <summary>
  ''' ディレクトリ オブジェクトの種類の名前リストを取得します。
  ''' </summary>
  ''' <value></value>
  ''' <returns>ディレクトリ オブジェクトの種類の名前リスト。</returns>
  ''' <remarks></remarks>
  Public Shared ReadOnly Property CategoryNames As ReadOnlyCollection(Of String)
    Get
      If _categoryNames Is Nothing Then
        _categoryNames = New ReadOnlyCollection(Of String)(
          {"ユーザー", "グループ", "コンピューター",
           "組織単位", "プリンター", "共有フォルダー", "外部のセキュリティ プリンシパル"})
      End If
      Return _categoryNames
    End Get
  End Property

  ''' <summary>
  ''' ドメインに接続できるかどうかを取得します。
  ''' </summary>
  ''' <value></value>
  ''' <returns>ドメインに接続できるかどうかを示す値。</returns>
  ''' <remarks></remarks>
  Public Shared ReadOnly Property CanConnectDomain As Boolean
    Get
      If _domainName Is Nothing Then
        SetData()   'データを設定
      End If
      Return _canConnect
    End Get
  End Property

  ''' <summary>
  ''' Active Directory ドメイン名を取得します。
  ''' </summary>
  ''' <value></value>
  ''' <returns>Active Directory ドメイン名。ドメインにログオンしていないまたは取得できない場合は <c>String.Empty</c>。</returns>
  ''' <remarks></remarks>
  Public Shared ReadOnly Property DomainName As String
    Get
      If _domainName Is Nothing Then
        SetData()   'データを設定
      End If
      Return _domainName
    End Get
  End Property

  ''' <summary>
  ''' ドメイングループのプライマリ グループ トークンと名前の組のリストを取得します。
  ''' このプロパティはドメインのグループを取得するメソッドを呼び出すたびに設定されます。
  ''' </summary>
  ''' <value></value>
  ''' <returns>プライマリ グループ トークンと名前の組のリスト。</returns>
  ''' <remarks></remarks>
  Public Shared ReadOnly Property GroupTokens As IDictionary(Of Integer, String)
    Get
      Return _groupTokens
    End Get
  End Property

  ''' <summary>
  ''' アプリケーションを実行しているユーザーがドメインにログオンしているかどうかを取得します。
  ''' </summary>
  ''' <value></value>
  ''' <returns>ユーザーがドメインにログオンしているかどうかを示す値。</returns>
  ''' <remarks></remarks>
  Public Shared ReadOnly Property IsLogonDomain As Boolean
    Get
      If _domainName Is Nothing Then
        SetData()   'データを設定
      End If
      Return _domLogon
    End Get
  End Property
#End Region

#Region " パブリックメソッド "
  ''' <summary>
  ''' 指定した Directory オブジェクトの使用されているリソースを解放します。
  ''' </summary>
  ''' <param name="items"><see cref="DirectoryObject"/> のコレクション。</param>
  ''' <exception cref="ArgumentNullException"><paramref name="items"/> が Nothing です。</exception>
  ''' <remarks></remarks>
  Public Shared Sub DisposeItems(items As IEnumerable(Of IDirectory))
    If items Is Nothing Then
      Throw New ArgumentNullException("items", "items が Nothing です。")
    End If

    For Each item In items
      item.Dispose()
    Next
  End Sub

  ''' <summary>
  ''' ユーザーの所属するグループを取得します。
  ''' </summary>
  ''' <param name="user">ユーザー。</param>
  ''' <returns>所属するグループのコレクション。</returns>
  ''' <exception cref="ArgumentNullException"><paramref name="user"/> が Nothing です。</exception>
  ''' <remarks></remarks>
  Public Shared Function GetBelongGroups(user As IUser) As ReadOnlyCollection(Of String)
    If user Is Nothing Then
      Throw New ArgumentNullException("user", "user が Nothing です。")
    End If

    Dim groups As New List(Of String)()
    For Each group As IADs In user.Native.Groups()  '所属するグループ数分(プライマリ グループ以外)
      groups.Add(group.Get("name").ToString())
    Next
    If TypeOf user Is DomainUser Then   'ドメインユーザーの時
      Dim primaryGroupId = DirectCast(user, DomainUser).PrimaryGroupId
      groups.Add(GroupTokens.Item(primaryGroupId))
    End If
    groups.Sort()
    Return groups.AsReadOnly()
  End Function

  ''' <summary>
  ''' 指定した <see cref="DirectoryEntry"/> の所属パスを取得します。
  ''' </summary>
  ''' <param name="entry"><see cref="DirectoryEntry"/>。</param>
  ''' <returns><paramref name="entry"/> の所属パス。所属していない場合は <see cref="String.Empty"/>。</returns>
  ''' <exception cref="ArgumentNullException"><paramref name="entry"/> が Nothing です。</exception>
  ''' <remarks></remarks>
  Public Shared Function GetBelongPath(entry As DirectoryEntry) As String
    If entry Is Nothing Then
      Throw New ArgumentNullException("entry", "entry が Nothing です。")
    End If

    Return GetBelongPath(entry.Path)
  End Function

  ''' <summary>
  ''' 指定した ADSI オブジェクトの所属パスを取得します。
  ''' </summary>
  ''' <param name="native">ADSI オブジェクト。</param>
  ''' <returns><paramref name="native"/> の所属パス。所属していない場合は <see cref="String.Empty"/>。</returns>
  ''' <exception cref="ArgumentNullException"><paramref name="adsiObject"/> が Nothing です。</exception>
  ''' <remarks></remarks>
  Public Shared Function GetBelongPath(native As IADs) As String
    If native Is Nothing Then
      Throw New ArgumentNullException("native", "native が Nothing です。")
    End If

    Return GetBelongPath(native.ADsPath)
  End Function

  ''' <summary>
  ''' 指定した LDAP パスの所属パスを取得します。
  ''' </summary>
  ''' <param name="ldapPath">LDAP パス。</param>
  ''' <returns><paramref name="ldapPath"/> の所属パス。所属していない場合は <see cref="String.Empty"/>。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetBelongPath(ldapPath As String) As String
    If ldapPath Is Nothing Then
      Return String.Empty
    End If

    If (ldapPath.Contains("OU=") OrElse ldapPath.Contains(",CN=")) = False Then   'ドメイン直下の時
      'OUにある場合は・・・LDAP://ドメイン名/CN=○○,OU=OU名,～
      'コンテナにある場合は・・・LDAP://ドメイン名/CN=○○,CN=コンテナ名,～
      Return String.Empty
    End If

    Dim spos = ldapPath.LastIndexOf("CN=")  '開始位置
    If spos < 0 Then  'OUの時
      spos = ldapPath.IndexOf("OU=")
    End If
    Dim epos = ldapPath.IndexOf(",DC=")     '終了位置
    Dim paths = ldapPath.Substring(spos, epos - spos).Split(","c)   '"CN=○○"と"OU=○○"部分の配列

    Array.Reverse(paths)
    If ldapPath.Contains("OU=") Then  'OUがある時
      If paths.Length = 1 Then  'ルートOU(自身)の時・・・LDAP://ドメイン名/OU=○○,DC=～
        Return String.Empty
      Else    'ルートOU以外の時
        ReDim Preserve paths(paths.Length - 2)  '自分の名前を除外
      End If
    End If

    Dim sb As New StringBuilder()
    For Each ou In paths
      sb.AppendFormat("{0}/", ou.Substring(3))
    Next
    sb.Length -= 1
    Return sb.ToString()
  End Function

  ''' <summary>
  ''' コンピューターを取得します。
  ''' </summary>
  ''' <returns>コンピューターのコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetComputers() As IList(Of Computer)
    Dim computers As New List(Of Computer)()
    If CanConnectDomain Then  'ドメインに接続できる時
      Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
        Dim filter = String.Format("(objectCategory={0})", CategoryType.Computer)
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              computers.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), Computer))
            Next
          End Using
        End Using
      End Using
    End If
    Return computers.OrderBy(Function(pc) pc.ToString()).ToList()
  End Function

  ''' <summary>
  ''' ドメインを取得します。取得できなかった場合、ドメインにログオンしていない場合は Nothing が返されます。
  ''' ドメインにログオンしている場合は例外がスローされます。
  ''' </summary>
  ''' <returns>ドメイン。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetDomain() As Domain
    Try
      _canConnect = True
      'Return Domain.GetCurrentDomain()
      Return Domain.GetComputerDomain()   'Webアプリを考慮するならこの方がいいかも
      'Dim context As New DirectoryContext(DirectoryContextType.Domain)
      'Return Domain.GetDomain(context)
    Catch
      _canConnect = False
      If _domLogon Then
        Throw
      End If
      Return Nothing
    End Try
  End Function

  ''' <summary>
  ''' グループを取得します。<see cref="GroupTokens"/> プロパティが設定されます。
  ''' </summary>
  ''' <typeparam name="T">Group オブジェクトの型。</typeparam>
  ''' <returns>グループのコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetGroups(Of T As {DirectoryObject, IGroup})() As IList(Of T)
    Dim groups As New List(Of T)()
    Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
      If CanConnectDomain Then  'ドメインに接続できる時
        Dim filter = String.Format("(objectCategory={0})", CategoryType.Group)
        GroupTokens.Clear()
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              Dim group = CreateInstance(res.GetDirectoryEntry())
              groups.Add(DirectCast(group, T))
              GroupTokens.Add(DirectCast(group, DomainGroup).Token, group.Name)   'プライマリ グループ トークンを追加
            Next
          End Using
        End Using
      Else  'ドメインに接続できない時
        root.Children.SchemaFilter.Add(CategoryType.Group.ToString())
        For Each entry As DirectoryEntry In root.Children
          groups.Add(DirectCast(CreateInstance(entry), T))
        Next
      End If
    End Using
    Return groups.OrderBy(Function(group) group.ToString()).ToList()
  End Function

  ''' <summary>
  ''' 指定したドメイングループのメンバーを取得します。
  ''' </summary>
  ''' <param name="group">グループ。</param>
  ''' <returns><paramref name="group"/> のメンバー。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetMembers(group As DomainGroup) As IList(Of DomainObject)
    If group Is Nothing Then
      Throw New ArgumentNullException("group", "group が Nothing です。")
    End If
    If CanConnectDomain = False Then  'ドメインに接続できない時
      Return New List(Of DomainObject)()
    End If

    Dim objects As New List(Of DomainObject)()
    Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
      'このグループのメンバーを検索
      Dim filter = String.Format("(memberOf={0})", group.Entry.Properties.Item("distinguishedName").Value)
      Using searcher As New DirectorySearcher(root, filter)
        Using results = searcher.FindAll()
          For Each res As SearchResult In results
            objects.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), DomainObject))
          Next
        End Using
        'このグループをプライマリ グループとしているメンバーを検索
        searcher.Filter = String.Format("(&(|(objectCategory={0})(objectCategory={1}))(primaryGroupID={2}))",
          CategoryType.User, CategoryType.Computer, group.Token)
        Using results = searcher.FindAll()
          For Each res As SearchResult In results
            objects.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), DomainObject))
          Next
        End Using
      End Using
    End Using
    Return objects.OrderBy(Function(o) o.ToString()).ToList()
  End Function

  ''' <summary>
  ''' 組織単位（OU）を取得します。
  ''' </summary>
  ''' <returns>組織単位（OU）のコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetOrganizationalUnits() As IList(Of OrganizationalUnit)
    Dim ous As New List(Of OrganizationalUnit)()
    If CanConnectDomain Then  'ドメインに接続できる時
      Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
        Dim filter = String.Format("(objectCategory={0})", CategoryType.OrganizationalUnit)
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              ous.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), OrganizationalUnit))
            Next
          End Using
        End Using
      End Using
    End If
    Return ous
  End Function

  ''' <summary>
  ''' 指定したプライマリ グループ トークンを持つドメイングループをプライマリ グループとしているメンバーの
  ''' <see cref="DirectoryEntry"/> のコレクションを取得します。
  ''' </summary>
  ''' <param name="primaryGroupToken">グループのプライマリ グループ トークン。</param>
  ''' <returns><paramref name="primaryGroupToken"/> のグループをプライマリ グループとしているメンバーの
  ''' <see cref="DirectoryEntry"/> のコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetPrimaryGroupMemberEntries(primaryGroupToken As Integer) As IList(Of DirectoryEntry)
    If CanConnectDomain = False Then  'ドメインに接続できない時
      Return New List(Of DirectoryEntry)()
    End If

    Dim entries As New List(Of DirectoryEntry)()
    Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
      Dim filter = String.Format("(&(|(objectCategory={0})(objectCategory={1}))(primaryGroupID={2}))",
        CategoryType.User, CategoryType.Computer, primaryGroupToken)
      Using searcher As New DirectorySearcher(root, filter)
        Using results = searcher.FindAll()
          For Each res As SearchResult In results
            entries.Add(res.GetDirectoryEntry())
          Next
        End Using
      End Using
    End Using
    Return entries
  End Function

  ''' <summary>
  ''' プリンターを取得します。
  ''' </summary>
  ''' <returns>プリンターのコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetPrintQueues() As IList(Of PrintQueue)
    Dim printers As New List(Of PrintQueue)()
    If CanConnectDomain Then  'ドメインに接続できる時
      Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
        Dim filter = String.Format("(objectCategory={0})", CategoryType.PrintQueue)
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              printers.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), PrintQueue))
            Next
          End Using
        End Using
      End Using
    End If
    Return printers.OrderBy(Function(printer) printer.ToString()).ToList()
  End Function

  ''' <summary>ユーザーを取得します。</summary>
  ''' <typeparam name="T">User オブジェクトの型。</typeparam>
  ''' <returns>ユーザーのコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetUsers(Of T As {DirectoryObject, IUser})() As IList(Of T)
    Dim users As New List(Of T)()
    Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
      If CanConnectDomain Then  'ドメインに接続できる時
        Dim filter = String.Format("(objectCategory={0})", CategoryType.User)
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              users.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), T))
            Next
          End Using

          If GroupTokens.Count = 0 Then   'ドメイングループのプライマリ グループ トークンと名前の組のリストがない時
            LoadGroupTokens(searcher)   'ドメイングループのプライマリ グループ トークンと名前の組のリストをロード
          End If
        End Using
      Else  'ドメインに接続できない時
        root.Children.SchemaFilter.Add(CategoryType.User.ToString())
        For Each entry As DirectoryEntry In root.Children
          users.Add(DirectCast(CreateInstance(entry), T))
        Next
      End If
    End Using
    Return users.OrderBy(Function(user) user.ToString()).ToList()
  End Function

  ''' <summary>
  ''' 共有フォルダーを取得します。
  ''' </summary>
  ''' <returns>共有フォルダーのコレクション。</returns>
  ''' <remarks></remarks>
  Public Shared Function GetVolumes() As IList(Of Volume)
    Dim folders As New List(Of Volume)()
    If CanConnectDomain Then  'ドメインに接続できる時
      Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
        Dim filter = String.Format("(objectCategory={0})", CategoryType.Volume)
        Using searcher As New DirectorySearcher(root, filter)
          Using results = searcher.FindAll()
            For Each res As SearchResult In results
              folders.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), Volume))
            Next
          End Using
        End Using
      End Using
    End If
    Return folders.OrderBy(Function(folder) folder.ToString()).ToList()
  End Function

  ''' <summary>
  ''' 指定した LDAP パスの名前(オブジェクト名)を取得します。
  ''' </summary>
  ''' <param name="ldapPath">LDAP パス。</param>
  ''' <returns>LDAP パスの名前(オブジェクト名)。<paramref name="ldapPath"/> が LDAP パスでない場合は <paramref name="ldapPath"/>。</returns>
  ''' <exception cref="ArgumentNullException"><paramref name="ldapPath"/> が Nothing です。</exception>
  ''' <remarks></remarks>
  Public Shared Function PathToCn(ldapPath As String) As String
    If ldapPath Is Nothing Then
      Throw New ArgumentNullException("ldapPath", "ldapPath が Nothing です。")
    End If

    Dim spos = ldapPath.IndexOf("="c) + 1
    If spos = 0 Then
      Return ldapPath
    End If

    Dim epos = ldapPath.IndexOf(","c)
    If epos > 0 Then
      Return ldapPath.Substring(spos, epos - spos)
    Else
      Return ldapPath.Substring(spos)
    End If
  End Function
#End Region

#Region " フレンドメソッド "
  ''' <summary>
  ''' 指定した名前と種類の Directory オブジェクトを検索します。
  ''' </summary>
  ''' <param name="name">検索する名前。</param>
  ''' <param name="objectCategory">Directory オブジェクトの種類。</param>
  ''' <returns>見つかった場合はそのオブジェクト。見つからなかった場合は Nothing。</returns>
  ''' <exception cref="ArgumentNullException"><paramref name="name"/> が Nothing です。</exception>
  ''' <exception cref="InvalidEnumArgumentException"><paramref name="objectCategory"/> が有効な <see cref="CategoryType"/> ではありません。</exception>
  ''' <remarks></remarks>
  Friend Shared Function FindDirectoryObject(name As String, objectCategory As CategoryType) As DirectoryObject
    If name Is Nothing Then
      Throw New ArgumentNullException("name", "name が Nothing です。")
    End If
    If IsValidCategoryType(objectCategory) = False Then
      Throw New InvalidEnumArgumentException("objectCategory が有効な CategoryType ではありません。")
    End If

    Using root = GetRootEntry()   'ルートのDirectoryEntryを取得
      If CanConnectDomain Then  'ドメインに接続できる時
        Dim filter As String
        Select Case objectCategory
          Case CategoryType.User
            filter = String.Format("(&(objectCategory={0})(sAMAccountName={1}))", objectCategory, name)
          Case CategoryType.OrganizationalUnit
            filter = String.Format("(&(objectCategory={0})(distinguishedName={1}))", objectCategory, name)
          Case CategoryType.PrintQueue
            filter = String.Format("(&(objectCategory={0})(printerName={1}))", objectCategory, name)
          Case Else
            filter = String.Format("(&(objectCategory={0})(name={1}))", objectCategory, name)
        End Select

        Using searcher As New DirectorySearcher(root, filter)
          Dim result = searcher.FindOne()
          Return If(result Is Nothing, Nothing, CreateInstance(result.GetDirectoryEntry()))
        End Using
      Else  'ドメインに接続できない時
        Return CreateInstance(root.Children.Find(name, objectCategory.ToString()))
      End If
    End Using
  End Function

  ''' <summary>
  ''' 指定した組織単位直下のオブジェクトを取得します。
  ''' </summary>
  ''' <param name="ou">組織単位。</param>
  ''' <returns><paramref name="ou"/>直下のオブジェクトのコレクション。</returns>
  ''' <remarks></remarks>
  Friend Shared Function GetStoredDomainObjects(ou As OrganizationalUnit) As List(Of DomainObject)
    Dim objects As New List(Of DomainObject)()
    Using searcher As New DirectorySearcher(ou.Entry)
      searcher.SearchScope = SearchScope.OneLevel
      Using results = searcher.FindAll()
        For Each res As SearchResult In results
          objects.Add(DirectCast(CreateInstance(res.GetDirectoryEntry()), DomainObject))
        Next
      End Using
    End Using
    Return objects.OrderBy(Function(o) o.Name).ToList()
  End Function
#End Region

#Region " プライベートメソッド "
  ''' <summary>
  ''' <see cref="DirectoryObject"/> のインスタンスを作成します。
  ''' </summary>
  ''' <param name="entry"><see cref="DirectoryEntry"/>。</param>
  ''' <returns><paramref name="entry"/> の <see cref="DirectoryObject"/> のインスタンス。</returns>
  ''' <exception cref="ArgumentException"><paramref name="entry"/> の種類が <see cref="CategoryType"/> に該当しません。</exception>
  ''' <remarks></remarks>
  Private Shared Function CreateInstance(entry As DirectoryEntry) As DirectoryObject
    Dim category As CategoryType
    If [Enum].TryParse(Of CategoryType)(entry.SchemaClassName, True, category) = False Then
      Throw New ArgumentException("entry の種類が CategoryType に該当しません。", "entry")
    End If

    Select Case category
      Case CategoryType.User
        If CanConnectDomain Then  'ドメインに接続できる時
          Return New DomainUser(entry)
        Else  'ドメインに接続できない時
          Return New LocalUser(entry)
        End If
      Case CategoryType.Group
        If CanConnectDomain Then  'ドメインに接続できる時
          Return New DomainGroup(entry)
        Else  'ドメインに接続できない時
          Return New LocalGroup(entry)
        End If
      Case CategoryType.Computer
        Return New Computer(entry)
      Case CategoryType.OrganizationalUnit
        Return New OrganizationalUnit(entry)
      Case CategoryType.PrintQueue
        Return New PrintQueue(entry)
      Case CategoryType.Volume
        Return New Volume(entry)
      Case Else
        Return New ForeignSecurityPrincipal(entry)
    End Select
  End Function

  ''' <summary>
  ''' ルートの <see cref="DirectoryEntry"/> を取得します。
  ''' </summary>
  ''' <returns>ルートの <see cref="DirectoryEntry"/>。</returns>
  ''' <remarks></remarks>
  Private Shared Function GetRootEntry() As DirectoryEntry
    If CanConnectDomain Then  'ドメインに接続できる時
      Using dom = GetDomain()   'ドメインを取得
        Return dom.GetDirectoryEntry()
      End Using
    Else  'ドメインに接続できない時
      Return New DirectoryEntry(_ntPath)
    End If
  End Function

  ''' <summary>
  ''' Directory オブジェクトの種類を検証します。
  ''' </summary>
  ''' <param name="category">Directory オブジェクトの種類。</param>
  ''' <returns>Directory オブジェクトの種類が有効な場合は true。それ以外の場合は false。</returns>
  ''' <remarks></remarks>
  Private Shared Function IsValidCategoryType(category As CategoryType) As Boolean
    Select Case category
      Case CategoryType.User, CategoryType.Group, CategoryType.Computer,
        CategoryType.OrganizationalUnit, CategoryType.PrintQueue, CategoryType.Volume, CategoryType.ForeignSecurityPrincipal
        Return True
      Case Else
        Return False
    End Select
  End Function

  ''' <summary>
  ''' ドメイングループのプライマリ グループ トークンと名前の組のリストをロードします。
  ''' </summary>
  ''' <param name="searcher"><see cref="DirectorySearcher"/>。</param>
  ''' <remarks></remarks>
  Private Shared Sub LoadGroupTokens(searcher As DirectorySearcher)
    searcher.Filter = String.Format("(objectCategory={0})", CategoryType.Group)
    Using results = searcher.FindAll()
      For Each res As SearchResult In results
        Dim group = DirectCast(CreateInstance(res.GetDirectoryEntry()), DomainGroup)
        GroupTokens.Add(group.Token, group.Name)  'プライマリ グループ トークンを追加
      Next
    End Using
  End Sub

  ''' <summary>
  ''' データを設定します。
  ''' </summary>
  ''' <remarks></remarks>
  Private Shared Sub SetData()
    _domainName = String.Empty
    _ntPath = String.Empty
    _domLogon = (String.Compare(Environment.MachineName, Environment.UserDomainName, True) <> 0)
    _groupTokens = New Dictionary(Of Integer, String)()

    Using domain = GetDomain()    'ドメインを取得
      If domain IsNot Nothing Then  '取得できた時
        _domainName = domain.Name
        Return
      End If
    End Using
    _ntPath = String.Format("WinNT://{0}", Environment.MachineName)
  End Sub
#End Region
End Class
